/*
Copyright (C) 2015 Marien Raat

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <random>

#define PI 3.14159265358979323846264338327950288419716939937510

class PerlinNoiseGenerator {
public:
    // Sets up the random generator for the PerlinNoiseGenerator and
    // initializes variables.
    PerlinNoiseGenerator(std::mt19937 *mt);

    // This function generates a perlin noise map and a gradient map
    // by calling this->generateGradientMap and
    // this->generatePerlinNoise according to the given arguments.
    // It returns a pointer to this->perlinNoiseMap.
    double *generatePerlinNoiseMap(
        // The width and height of the to be generated perlin noise
        // map
        int width,
        int height,
        // The size of the features of the perlin noise map. This
        // determines the ratio between the size of the gradient map
        // and the size of the perlin noise map.
        double featureSize,
        // Whether to overwrite the previous gradient map or allocate
        // new space.
        // If the gradient map size is different from the previous
        // size this decides whether the old map will be deleted,
        // since new space needs to be allocated regardless.
        bool overwriteOldGradientMap = true,
        // Whether to overwrite the previous perlin noise map or
        // allocate new space.
        // If the perlin noise map size is different from the previous
        // size this decides whether the old map will be deleted,
        // since new space needs to be allocated regardless.
        bool overwriteOldPerlinNoiseMap = true);

    // Generates a gradient map filled with random gradient vectors
    // needed for the generation of the perlin noise.
    // The gradient map will be saved in this->gradientMap.
    void generateGradientMap(
        // The width and height of the gradient map that will be
        // generated
        int width,
        int height,
        // Whether to overwrite the previous gradient map or allocate
        // new space.
        // If the gradient map size is different from the previous
        // size this decides whether the old map will be deleted,
        // since new space needs to be allocated regardless.
        bool overwriteOldGradientMap = true);

    // This function generates an perlin noise map and saves it to
    // this->perlinNoiseMap, based on the
    // gradientMap. this->generateGradientMap needs to be generated 
    void generatePerlinNoise(
        // The ammount of points generated per cell in the
        // gradientMap.
        // The size of the generated perlin noise map will be
        // (pointsPerCell * gradientMapWidth) by (pointsPerCell *
        // gradientMapHeight).
        double pointsPerCell,
        // Whether to overwrite the previous perlin noise map or
        // allocate new space.
        // If the perlin noise map size is different from the previous
        // size this decides whether the old map will be deleted,
        // since new space needs to be allocated regardless.
        bool overwriteOldPerlinNoiseMap = true);

    // The height and with of the current gradient map
    int gradientMapWidth, gradientMapHeight;
    // A 1-dimensional array of the size [gradientMapHeight *
    // gradientMapWidth * 2]. Where the first element at location
    // signifies the x component of the gradient vector and the second
    // element the y component. So the x and y component at (xc, yc)
    // would be accessed by:
    // x = gradientMap[2 * (xc + yc * gradientMapWidth) + 0]
    // y = gradientMap[2 * (xc + yc * gradientMapWidth) + 1]
    // This gradient map is filled by random gradient vectors when
    // this->generateGradientmap is called.
    double *gradientMap;

    // The height and width of the current perlin noise map
    int perlinNoiseMapWidth, perlinNoiseMapHeight;
    // A 1 dimensional array wich stores the current perlin noise
    // map. It has the size perlinNoiseMapWidth *
    // perlinNoiseMapHeight.
    // This map is filled and generated when the
    // this->generatePerlinNoise is called.
    double *perlinNoiseMap;
private:
    std::mt19937 *mt;
    std::uniform_real_distribution<double> *directionalDistribution;

    bool firstGradientMap, firstPerlinNoiseMap;

    // Gets the perlin noise value at (x, y) based on the gradientMap
    double getPerlinNoiseValue(double x, double y);
    // Calculates the dot product between the direction vector from
    // the point to the grid and the gradient pointer on the grid
    // point
    double getGridGradientDotProduct(double pointX,
                                     double pointY,
                                     int gridX,
                                     int gridY);
    // Calculates a value based on the two values and the relative
    // weight of v1 in the range [0, 1].
    double getLinearInterpolation(double v1, double v2
                                  , double weight);
    double getInterpolationSCurve(double distance);
};
